/**
 * This file is part of 1genia trampoline
 * Copyright (C) 2007 1genia (contact@1genia.com)
 *
 * This library is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; version 3 of the License. 
 *
 * This library is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details. 
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; see the file COPYING.TXT.  If not,
 * write to the Free Software Foundation, Inc., 51 Franklin Street,
 * Fifth Floor, Boston, MA 02110-1301, USA. 
 **/
package com.genia.toolbox.uml_generator.manager.impl;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.HashSet;
import java.util.Set;

import com.genia.toolbox.basics.bean.impl.ConjunctionPredicate;
import com.genia.toolbox.basics.exception.BundledException;
import com.genia.toolbox.basics.exception.technical.TechnicalIOException;
import com.genia.toolbox.uml_generator.Constants;
import com.genia.toolbox.uml_generator.bean.ModelGeneratorContext;
import com.genia.toolbox.uml_generator.manager.ModelGenerator;
import com.genia.toolbox.uml_generator.manager.impl.ModelDecoratorImpl.GenericObject;
import com.genia.toolbox.uml_generator.model.AbstractClassModel;
import com.genia.toolbox.uml_generator.model.AbstractJavaObject;
import com.genia.toolbox.uml_generator.model.AttributeModel;
import com.genia.toolbox.uml_generator.model.DataTypeModel;
import com.genia.toolbox.uml_generator.model.OperationModel;

/**
 * manager to generate the model of the interfaces from the UML datas.
 */
public class InterfaceGeneratorImpl
    extends AbstractManager
    implements ModelGenerator
{

  /**
   * the generator.
   * 
   * @param context
   *          the context of the generation
   * @throws BundledException
   *           when an error occurred
   * @see com.genia.toolbox.uml_generator.manager.ModelGenerator#generate(com.genia.toolbox.uml_generator.bean.ModelGeneratorContext)
   */
  @SuppressWarnings("unchecked")
  public void generate(ModelGeneratorContext context)
      throws BundledException
  {
    for (AbstractClassModel classModel : getCollectionManager().filterList(context.getClasses().values(),
        new ConjunctionPredicate<AbstractClassModel>(Constants.KEEP_ONLY_INTERFACE_PREDICATE, Constants.REMOVE_IGNORED_CLASS_PREDICATE))) {
      generateInterface(context, classModel);
    }
  }



  /**
   * generate the static variable used to reference a property in the
   * persistence API.
   * 
   * @param out
   *          the {@link PrintWriter} to write to
   * @param attribute
   *          the attribute to write the property for
   * @throws TechnicalIOException
   *           when an error occurred
   */
  private void generatePropertiesIdentifier(PrintWriter out, AttributeModel attribute)
      throws TechnicalIOException
  {
    DataTypeModel propertyFieldType = null;
    AbstractJavaObject[] genericTypes = null;
    final AbstractJavaObject attributeType = attribute.getType().getObject();
    if (attributeType instanceof GenericObject) {
      genericTypes = ((GenericObject) attributeType).getGenericTypes();
      propertyFieldType = getModelDecorator().getGenericObject(Constants.PROPERTY_FIELD_MODEL, attributeType, genericTypes[genericTypes.length - 1]);
    }
    else {
      propertyFieldType = getModelDecorator().getGenericObject(Constants.SIMPLE_PROPERTY_FIELD_MODEL, attributeType);
    }
    getPrinterManager().printComment(out, "the name of the " + attribute.getName() + " property.");
    out.append("public static final ").append(propertyFieldType.getQualifiedName()).append(" NAME_").append(getNameManager().getUpperUnderlineName(attribute.getUndecoraredObject())).append(" = ")
        .append(Constants.PROPERTY_FIELD_MODEL.getQualifiedName()).append(".create(").append(attribute.getType().getUndecoraredObject().getQualifiedName()).append(".class, ");
    if (genericTypes != null) {
      out.append(genericTypes[genericTypes.length - 1].getUndecoraredObject().getQualifiedName()).append(".class, ");
    }
    out.append("\"").append(attribute.getName()).append("\");\n");
  }



  /**
   * generate the static variables used to reference a property in the
   * persistence API.
   * 
   * @param out
   *          the {@link PrintWriter} to write to
   * @param classModel
   *          the model to write the properties for
   * @throws TechnicalIOException
   *           when an error occurred
   */
  private void generatePropertiesIdentifiers(PrintWriter out, AbstractClassModel classModel)
      throws TechnicalIOException
  {
    for (AttributeModel attribute : getAttributeManager().newAttributesForInterface(classModel)) {
      generatePropertiesIdentifier(out, attribute);
    }
  }



  /**
   * return the {@link Set} of {@link OperationModel} that are new to the
   * {@link AbstractClassModel} given in parameter.
   * 
   * @param classModel
   *          the {@link AbstractClassModel} to inspect.
   * @return the {@link Set} of {@link OperationModel} that are new to the
   *         {@link AbstractClassModel} given in parameter
   */
  private Set<OperationModel> newOperations(AbstractClassModel classModel)
  {
    Set<OperationModel> res = new HashSet<OperationModel>(classModel.getOperationModel());
    if (classModel.getParent() != null) {
      res.removeAll(classModel.getParent().getOperationModel());
    }
    return res;
  }



  /**
   * return the {@link Set} of {@link OperationModel} that are new to the
   * {@link AbstractClassModel} given in parameter.
   * 
   * @param classModel
   *          the {@link AbstractClassModel} to inspect.
   * @return the {@link Set} of {@link OperationModel} that are new to the
   *         {@link AbstractClassModel} given in parameter
   */
  public Set<OperationModel> newOperationsForInterface(AbstractClassModel classModel)
  {
    Set<OperationModel> res = newOperations(classModel);
    for (AbstractClassModel interfaceModel : classModel.getImplementsModel()) {
      res.removeAll(interfaceModel.getOperationModel());
    }
    return res;
  }



  /**
   * generate the interface.
   * 
   * @param context
   *          the context of the generation
   * @param classModel
   *          the model of the interface to generate.
   * @throws BundledException
   *           when an error occurred
   */
  private void generateInterface(ModelGeneratorContext context, AbstractClassModel classModel)
      throws BundledException
  {
    File classFile = new File(context.getDirectoryForPackage(context.getConfiguration().getGeneratedSrcDir(), classModel), classModel.getName() + ".java");
    classFile.getParentFile().mkdirs();
    try {
      PrintWriter out = new PrintWriter(new FileOutputStream(classFile));
      context.getGeneratedSources().add(classFile);

      getPrinterManager().printPackage(out, classModel).println();

      getPrinterManager().printComment(out, classModel.getComment(), Constants.COMMENT_WARNING_GENERATED);
      out.append("public interface ").append(classModel.getName()).println();
      getPrinterManager().printDependencies(context, out, classModel).append("{");

      generatePropertiesIdentifiers(out, classModel);

      for (OperationModel operation : newOperationsForInterface(classModel)) {
        getPrinterManager().printDeclaration(out, operation);
      }
      for (AttributeModel attribute : getAttributeManager().newAttributesForInterface(classModel)) {
        getPrinterManager().printDeclaration(out, getAttributeManager().getGetter(attribute));
        getPrinterManager().printDeclaration(out, getAttributeManager().getSetter(attribute));
      }
      for (OperationModel operation : getAssociationManager().getAccessorMethodForAssociations(getAssociationManager().newAssociationsForInterface(classModel))) {
        getPrinterManager().printDeclaration(out, operation);
      }

      out.append("}");
      out.close();
    }
    catch (IOException e) {
      throw getExceptionManager().convertException(e);
    }
  }

}
